//
// AppInterface.cpp
// libprotea
// Tools Library for the Protea Project / Application Handler Interface, UNIX specific code
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../OS_defines.h"
#ifdef OS_UNIX

#include "../defines.h"
#include "AppInterface.h"

void XCAppInterface::GetMessage( XCMsgPortPtr parent, XCMsgPortPtr* childlist, XCMsgPortPtr listener )
{
	INFUNC( XCAppInterface::GetMessage, this )

ToolsTrace( TRC_MESSAGING, TLV_HIGH, "Check for a new message" );

	// Check to see if there is anything to listen for
	if (!parent && !listener && (!childlist || !*childlist))
		EXITFUNC();

	XCMsgPortPtr tempport;

	ToolsTrace( TRC_TRAFFIC, TLV_HIGH, "GetMessage" );

	// unix/linux/posix/whateverix systems do need them to be recreated each time...
	timeval tv;
	tv.tv_sec = 2;
	tv.tv_usec = 0;

	// Create select list
	ports.Create();

	if (listener) {
		assert( listener->transport );
		ToolsTrace( TRC_MESSAGING, TLV_HIGH, "Add listener" );
//		ToolsTrace( TRC_MESSAGING, TLV_HIGH, "Add listener: %ld", (long)listener->transport->GetInHandle() );
		ports.Add( listener );
	}

	if (parent) {
		assert( parent->transport );
//		ToolsTrace( TRC_MESSAGING, TLV_HIGH, "Add parent: %ld", (long)parent->transport->GetInHandle() );
		ports.Add( parent );
	}

	ToolsTrace( TRC_MESSAGING, TLV_HIGH, "Check childlist %ld", childlist );
	childlistlock.Lock();
	if (*childlist) {
		tempport = *childlist;
		while (tempport != NULL) {
			assert( tempport->transport );
			ports.Add( tempport );
			tempport = tempport->next;
		}
	}
	childlistlock.UnLock();

	long retval;
	XCMsgPortPtr prevport;
	retval = ports.Select( NULL );
	if (retval > 0) {
		// check parent port for data to read
		if (parent && CheckPort( parent, &ports ) ) {
			OnDroppedConnection( parent );
			delete parent;
			parent = NULL;
		}

		// Handle new connections from listener socket
		if ( listener && ports.HasData( listener ) ) {
			XCSocketTransport* list_sock = (XCSocketTransport*)listener->transport;
			try {
				XCSocketTransport* new_socket = list_sock->Accept();

				childlistlock.Lock();
				tempport = *childlist;
				if (tempport)
					while ( tempport->next != NULL )
						tempport = tempport->next;
				// check for total # of sockets
				// if over limit, then
				// ToolsCloseSocket( new_socket );
				// else

				XCMsgPortPtr new_port = new XCMsgPort( NULL, this );
				if (tempport)
					tempport->next = new_port;
				else
					*childlist = new_port;
				childlistlock.UnLock();

				new_port->transport = new_socket;
				new_socket->SetBlockingMode( false );
			} catch( XCToolsError& error ) {
				error.Clear();
				listener = 0;
			};
		};

		// check each child port for data to read
		childlistlock.Lock();
		try {
			tempport = *childlist;
			prevport = NULL;
			while ( tempport ) {
				ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Checking %d", tempport->transport->GetInHandle());
				if (CheckPort( tempport, &ports ) == -1) {
					ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Removing %d", tempport->transport->GetInHandle() );
					OnDroppedConnection( tempport );
					tempport = RemoveChild( childlist, tempport, prevport );
				} else {
					assert( tempport );
					ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Check done" );

					// Move on to the next item in the list
					prevport = tempport;
					tempport = tempport->next;
				}
			}
		} catch( ... ) {
			childlistlock.UnLock();
			throw;
		};
		childlistlock.UnLock();
	} else {
		// Death message
		if (retval == 0)
			abort = true;

		// Socket/Pipe error
		if (retval == -1)
		{
			ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Error : Select Failed -> %d", errno );
			if (errno == 4)
				ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Ignoring error EINTR" );
			else
				RAISEOSERROR();
		};
	};

	OUTFUNC()
};

int XCAppInterface::CheckPort( XCMsgPortPtr port, XCPortHandler* portset )
{
	INFUNC( XCAppInterface::CheckPort, this )

	base_message header;
	int retval;

	// Check to see if our port has outstanding data to be read
	if ( ports.HasData( port ) ) {
		// Handle case of a new message
		if (port->awaiting_header) {
			ToolsTrace( TRC_TRAFFIC, TLV_HIGH, "Reading initial header" );

			try {
				retval = port->transport->Recv( &header, BASE_MESSAGE_SIZE );
			} catch( ... ) {
				RETURNFUNC( -1 );
			};

			if (retval != BASE_MESSAGE_SIZE ) {
//				if (retval == 0) {
//					RETURNFUNC( -1 );
//					// flag for broken connection / removal of object from list
//				};
//				ToolsTrace( TRC_TRAFFIC, TLV_HIGH, "Get Message: %d, size = %d, wanted = %d", errno, retval, BASE_MESSAGE_SIZE );
				RETURNFUNC( -1 );
			};


			// Check for valid data
			if (header.type < 0 || header.type >= msg_last || header.miscflag > 112 ||
				header.data_size > 64 * 1024) {

				ToolsTrace( TRC_TRAFFIC, TLV_HIGH, "Invalid message!" );
				RETURNFUNC( -1 );
			};

			ToolsTrace( TRC_TRAFFIC, TLV_HIGH, "Get Message: Received header, data_size = %ld, orig_size = %ld, type = %d", header.data_size, header.orig_size , header.type);

			port->awaiting_header = false;

			// Allocate a new message
			port->message->CreateMsg( &header );

			if (header.data_size > 0 ) {
				port->expected = header.data_size;
				port->received = 0;
			};
		};

		// We already have a header, continue reading
		ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Awaiting %d bytes", port->expected );
		try {
			retval = port->transport->Recv((char*)port->message->GetCompressPtr() + port->received, port->expected );
		} catch( ... ) {
			RETURNFUNC( 0 );
		};

		ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, "Received %d bytes", retval );

		port->expected -= retval;
		port->received += retval;

		ToolsTrace( TRC_APPINTERFACE, TLV_MEDIUM, "Get Message %d", port->message->GetHeaderPtr()->type );
		ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, " -- expected : %d", port->expected );
		ToolsTrace( TRC_APPINTERFACE, TLV_HIGH, " -- received : %d", port->received );
		
		if ( port->expected == 0 ) {
			port->awaiting_header = true;
			port->message->Dump();
			port->message->Decompress();
			if ( GotMessage( port, port->message ) )
				port->message->DeleteMsg(); // We are done with the message.  Recycle the memory
		};
	};

	OUTFUNCRET( 0 )
};

void XCAppInterface::CancelMessageLoop() {

//	aborting = true;

	if (!GetAbortFlag()) {
		// Raise the abort event
		abort.Raise();
		abort = true;
		message_loop_running = false;
	};
};

void XCAppInterface::StartMessageLoop( route_loc dest ) {
	current_loc = dest;
	abort.Reset();
	message_loop_running = true;
};


#endif // OS_UNIX
